﻿//----------------------------------------------------------------
// Copyright (c) 2011-2013 
// All rights reserved.
// Author: Ben DaoWei 
// Contact: bendaowei@gmail.com
// Web Link: http://xnaeditor.codeplex.com
// ----------------------------------------------------------------

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using System.Diagnostics;
using UnityEngine.Terrain;
using UnityEngine.Components;
using UnityEngine.Graphics.Shader;
using UnityEngine.Graphics.Text;
using UnityEngine.Gameplay.Match;
using UnityEngine.Gameplay.Player;

namespace UnityEngine.Graphics
{
    #region 2D Graphics
    public class TwoDInstance : PaletteInstance
    {
        public TwoDInstance(Vector3 Position, Vector3 Rotation, ERenderState RenderStates, PaletteRenderer palette)
            : base(Position, Rotation, RenderStates, palette)
        {
        }

        public virtual void Draw()
        { }

        public virtual void Draw(int screenWidth, int screenHeight)
        {
            this.Draw();
        }
    }

    [Flags]
    public enum TextAlignment
    {
        Left = 0,
        Right = 1,
        Middle = 2,
    }

    public class TextInstance : TwoDInstance
    {
        private string _Text;
        private Vector2 _Position;
        private Color _Color;

        public string Text
        {
            get { return _Text; }
            set { _Text = value; }
        }

        public Vector2 Position
        {
            get { return _Position; }
            set { _Position = value; }
        }

        public Color Color
        {
            get { return _Color; }
            set { _Color = value; }
        }

        public TextAlignment TextAlignment
        {
            get;
            set;
        }

        public TextInstance(string text, Vector2 position, Color color, PaletteRenderer palette)
            : base(Vector3.Zero, Vector3.Zero, 0, palette)
        {
            this._Text = text;
            this._Position = position;
            this._Color = color;
        }

        public override void Draw(int ScreenWidth, int ScreenHeight)
        {
            Vector2 position;
            position.X = _Position.X * ScreenWidth / SceneManager.DefaultScreenWidth;
            position.Y = _Position.Y * ScreenHeight / SceneManager.DefaultScreenHeight;

            SpriteFont font = FontStorage.Instance.LoadFont("Shader//hudFont");
            DrawString(font, _Text, position, TextAlignment, _Color);
        }

        public override void Draw()
        {
            throw new Exception("Not supported");
        }

        public void DrawString(SpriteFont font, string text, Vector2 position, TextAlignment align, Color color)
        {
            Vector2 size = font.MeasureString(text);

            if (align.HasFlag(TextAlignment.Left)) { }

            if (align.HasFlag(TextAlignment.Right)) position.X += -size.X;

            if (align.HasFlag(TextAlignment.Middle)) position.X += -size.X / 2;

            SpriteBatch spriteBatch = (this.PaletteReference.PaletteRenderer as SpriteRenderer).SpriteBatch;
            spriteBatch.DrawString(font, text, position, color, 0, Vector2.Zero, 1.0f, SpriteEffects.None, 0);
        }
    }

    public class SpriteInstance : TwoDInstance
    {
        private int _X;
        public int X
        {
            set
            {
                _X = value;
            }
        }
        private int _Y;
        public int Y
        {
            set
            {
                _Y = value;
            }
        }
        private int _Width;
        private int _Height;

        private float _SX;
        private float _SY;
        private float _SWidth;
        private float _SHeight;

        private Texture2D _Texture;

        public Texture2D Texture
        {
            get
            {
                return _Texture;
            }
            set
            {
                _Texture = value;
            }
        }

        public SpriteInstance(int X, int Y, int Width, int Height,
                              float SX, float SY, float SW, float SH,
                              Texture2D texture, PaletteRenderer palette)
            : base(Vector3.Zero, Vector3.Zero, 0, palette)
        {
            _X = X;
            _Y = Y;
            _Width = Width;
            _Height = Height;

            _SX = SX;
            _SY = SY;
            _SWidth = SW;
            _SHeight = SH;

            _Texture = texture;
        }

        public void Draw()
        {
            // render in default size
            Draw(SceneManager.DefaultScreenWidth, SceneManager.DefaultScreenHeight);
        }

        public override void Draw(int ScreenWidth, int ScreenHeight)
        {
            if (_Texture == null)
                return;

            int Width = _Texture.Width;
            int Height = _Texture.Height;

            int SX = (int)(_SX * Width);
            int SY = (int)(_SY * Height);
            int SWidth = (int)(_SWidth * Width);
            int SHeight = (int)(_SHeight * Height);

            int TX = _X * ScreenWidth / SceneManager.DefaultScreenWidth;
            int TY = _Y * ScreenHeight / SceneManager.DefaultScreenHeight;
            int TW = _Width * ScreenWidth / SceneManager.DefaultScreenWidth;
            int TH = _Height * ScreenHeight / SceneManager.DefaultScreenHeight;

            if (this.PaletteReference.PaletteRenderer is CusmtomBlendSpriteRenderer)
            {
                CusmtomBlendSpriteRenderer r = (this.PaletteReference.PaletteRenderer as CusmtomBlendSpriteRenderer);
                r.Mode = this.Mode;
                r.Percent = this.Percent;
                r.Width = TW;
                r.Height = TH;
            }
            SpriteBatch spriteBatch = (this.PaletteReference.PaletteRenderer as SpriteRenderer).SpriteBatch;
            spriteBatch.Draw(_Texture, new Rectangle(TX, TY, TW, TH), new Rectangle(SX, SY, SWidth, SHeight), Color.White);
        }

        public int Mode
        {
            get;
            set;
        }

        public float Percent
        {
            get;
            set;
        }
    }
    #endregion

    #region 3D Graphics
    public class PaletteInstance : IDisposable
    {
        public PaletteInstance(Vector3 Position, Vector3 Rotation, ERenderState RenderStates, PaletteRenderer palette)
        {
            this.Position = Position;
            this.Orientation = Matrix.CreateFromYawPitchRoll(Rotation.X, Rotation.Y, Rotation.Z);
            this.RenderStates = RenderStates;
            this.PaletteReference = new PaletteReference(palette);
            this.IsMovable = false;
            this.IsVisible = true;
            ModelRenderer r = (this.PaletteReference.PaletteRenderer as ModelRenderer);
            if (r != null)
                this.IsTransparent = (r).IsTransparent();
            else
                this.IsTransparent = false;
            this.IsCreateShadow = false;
            this.IsTerrain = false;
            this.RenderQueueValue = 0;
        }

        public bool IsTransparent;
        public Vector3 Scale;
        private Vector3 _Position;
        public Vector3 Position
        {
            get
            {
                return _Position;
            }
            set
            {
                _Position = value;
            }
        }
        public Matrix Orientation;
        public ERenderState RenderStates;
        public bool IsTerrain;
        public bool IsInBoundingFrustum;
        public bool IsVisible;
        public bool IsMovable;
        public Vector3 SelectableCircleScale = Vector3.One * 1.0f;
        public bool IsCreateShadow;
        public int RenderQueueValue = 0;
        public SequenceAnimationPlayer Player;

        public PaletteReference PaletteReference;

        ~PaletteInstance()
        {
            this.Dispose(false);
        }

        public void CalculateRenderQueueValue(int offset = 0)
        {
            if (this is TwoDInstance)
            {
                this.RenderQueueValue = offset;
                if (this is TextInstance)
                    this.RenderQueueValue = 10240 + offset;
                //const int BaseValue = 10240;
                //if (this.PaletteReference.PaletteRenderer is AlphaBlendSpriteRenderer)
                //    this.RenderQueueValue = BaseValue * 0 + offset;
                //else
                //    if (this.PaletteReference.PaletteRenderer is OpaqueBlendSpriteRenderer)
                //        this.RenderQueueValue = BaseValue * 1 + offset;
                //    else
                //        if (this.PaletteReference.PaletteRenderer is AlphaTestBlendSpriteRenderer)
                //            this.RenderQueueValue = BaseValue * 2 + offset;
                //        else
                //            throw new Exception("Two D Instance has a new undefined type");
            }
            else
            {
                if (this.IsTerrain)
                    this.RenderQueueValue = 0;
                else
                    this.RenderQueueValue = 5000;
            }
            this.RenderQueueValue += offset;
        }
        public void Dispose()
        {
            this.Dispose(true);
            GC.SuppressFinalize(this);
        }

        protected virtual void Dispose(bool disposing)
        {
            if (disposing)
            {
                if (this.PaletteReference != null)
                {
                    ModelRenderer renderer = this.PaletteReference.PaletteRenderer as ModelRenderer;
                    if (renderer != null)
                        renderer.Dispose();
                }
            }
        }

        public virtual void Update(GameTime gameTime)
        {
            // empty
        }

        public virtual void Update(BoundingFrustum BoundingFrustum)
        {
            if (BoundingFrustum.Contains(this.Position) != ContainmentType.Disjoint)
                IsInBoundingFrustum = true;
            else
                IsInBoundingFrustum = false;
        }

        public virtual bool Select(Ray ray)
        {
            return false;
        }

        public virtual ContainmentType BeContained(BoundingBox BoundingBox)
        {
            return BoundingBox.Contains(this.Position);
        }

        public virtual void Draw(Matrix view, Matrix projection)
        {
            if (IsInBoundingFrustum == false)
                return;

            ModelRenderer renderer = this.PaletteReference.PaletteRenderer as ModelRenderer;
            Matrix world = Orientation * Matrix.CreateTranslation(this.Position);
            renderer.Draw(world, view, projection);
        }
    }

    public class RendererInstance : PaletteInstance
    {
        private Vector3 _Offset = Vector3.Zero;
        private Vector3 _Scale = Vector3.One;

        private Renderer _Renderer;
        public Renderer Renderer
        {
            get
            {
                return _Renderer;
            }
            set
            {
                _Renderer = value;
            }
        }

        private BoundingBox _BoundingBox;
        public BoundingBox BoundingBox
        {
            get
            {
                return _BoundingBox;
            }
            set
            {
                _BoundingBox = value;
            }
        }

        public ModelRenderer ModelRenderer
        {
            get
            {
                return this.PaletteReference.PaletteRenderer as ModelRenderer;
            }
        }
        public TeamRelationType TeamType
        {
            get;
            set;
        }

        public RendererInstance(Transform transformer, ModelRenderer modelRenderer, Vector3 offset, Vector3 scale)
            : base(transformer.Position, Vector3.Zero, ERenderState.Opaque | ERenderState.EnableDepth | ERenderState.CullCounterClockWise | ERenderState.Solid, modelRenderer)
        {
            this._Scale = scale;
            this._Offset = offset;
            this.IsTerrain = false;
            this.CalculateRenderQueueValue();
        }

        public override void Update(GameTime gameTime)
        {
            ModelRenderer renderer = this.PaletteReference.PaletteRenderer as ModelRenderer;
            renderer.Update(gameTime);

            base.Update(gameTime);
        }

        public Matrix World
        {
            get
            {
                Matrix world = Matrix.Identity;
                if (this.Renderer != null)
                    world = this.Renderer.GameObject.GetComponent<Transform>().WorldMatrix(this._Scale, this._Offset);
                else
                {
                    world = this.Orientation * Matrix.CreateScale(this._Scale) * Matrix.CreateTranslation(this.Position + this._Offset);
                }
                return world;
            }
        }

        public override void Draw(Matrix view, Matrix projection)
        {
            if (IsInBoundingFrustum == false || this.IsVisible == false)
                return;

            // update team color
            ModelRenderer renderer = this.PaletteReference.PaletteRenderer as ModelRenderer;
            renderer.UpdateTeam(this.TeamType);

            // update animation
            if (this.Player != null)
            {
                renderer.UpdateAnimationTick(this.Player.CurrentTick);
            }

            if (this._Renderer != null && this._Renderer.GameObject.GetComponent<AttackWeaponObject>() != null)
            {
                int p = 0;
            }
            // calculate the world matrix
            Matrix world = this.World;

            // calcualte fog
            Vector3 position = new Vector3(world.M41, world.M42, world.M43);
            float value = SceneManager.Instance.Game.GameScene.GetComponentInChildren<Landscape>().WarCraftEnvironment.GetLightValue(position);
            EffectManager.Instance.Shader.Fog = value;

            renderer.Draw(world, view, projection);

            EffectManager.Instance.Shader.Fog = 1.0f;
        }
    }

    public class LandscapeCellInstance : PaletteInstance
    {
        private BoundingBox _BoundingBox;

        public LandscapeCellInstance(LandscapeCellRenderer Renderer)
            : base(Renderer.Position, Vector3.Zero, ERenderState.Opaque | ERenderState.EnableDepth | ERenderState.CullCounterClockWise | ERenderState.Solid, Renderer)
        {
            _BoundingBox = Renderer.BoundingBox;
            IsCreateShadow = true;
            IsTerrain = true;
            this.CalculateRenderQueueValue();
        }

        public override void Update(BoundingFrustum BoundingFrustum)
        {
            if (BoundingFrustum.Contains(this._BoundingBox) != ContainmentType.Disjoint)
                IsInBoundingFrustum = true;
            else
                IsInBoundingFrustum = false;
        }

        public override ContainmentType BeContained(BoundingBox BoundingBox)
        {
            return BoundingBox.Contains(_BoundingBox);
        }

        public override void Draw(Matrix view, Matrix projection)
        {
            if (IsInBoundingFrustum == false)
                return;

            LandscapeCellRenderer renderer = this.PaletteReference.PaletteRenderer as LandscapeCellRenderer;
            renderer.Draw(Matrix.Identity, view, projection);
        }

        protected override void Dispose(bool disposing)
        {
            if (disposing)
            {
                if (this.PaletteReference != null)
                {
                    LandscapeCellRenderer renderer = this.PaletteReference.PaletteRenderer as LandscapeCellRenderer;
                    if (renderer != null)
                        renderer.Dispose();
                }
            }
            base.Dispose(disposing);
        }
    }

    public class CliffInstance : PaletteInstance
    {
        private Texture2D _Texture;
        private Vector3[] _Positions;
        private bool _IsCliffTrans;
        private EDirection _Direction;
        private ESide _Side;
        private BoundingBox _BoundingBox;

        private CliffRenderer _CliffRenderer;

        public CliffInstance(Vector3 Position0, Vector3 Position1, Vector3 Position2, Vector3 Position3, Vector3 Position4, Vector3 Position5,
                             bool isCliffTrans, EDirection direction, ESide side,
                             Texture2D texture, Vector3 Position, Vector3 Rotation, ERenderState RenderStates, CliffRenderer palette) :
            base(Position, Rotation, RenderStates, palette)
        {
            this._Texture = texture;
            _Positions = new Vector3[6];
            _Positions[0] = Position0;
            _Positions[1] = Position1;
            _Positions[2] = Position2;
            _Positions[3] = Position3;
            _Positions[4] = Position4;
            _Positions[5] = Position5;
            _IsCliffTrans = isCliffTrans;
            _Direction = direction;
            _Side = side;

            Vector3 Min = new Vector3(float.MaxValue, float.MaxValue, float.MaxValue);
            Vector3 Max = new Vector3(float.MinValue, float.MinValue, float.MinValue);
            var MaxIndex = (_IsCliffTrans ? 6 : 4);
            for (int i = 0; i < MaxIndex; i++)
            {
                Min.X = Math.Min(_Positions[i].X, Min.X);
                Max.X = Math.Max(_Positions[i].X, Max.X);

                Min.Y = Math.Min(_Positions[i].Y, Min.Y);
                Max.Y = Math.Max(_Positions[i].Y, Max.Y);

                Min.Z = Math.Min(_Positions[i].Z, Min.Z);
                Max.Z = Math.Max(_Positions[i].Z, Max.Z);
            }

            _BoundingBox = new BoundingBox(Min, Max);

            _CliffRenderer = (this.PaletteReference.PaletteRenderer as CliffRenderer).Copy();
            this.PaletteReference = new PaletteReference(_CliffRenderer);
            _CliffRenderer.UpdateRelpacableTexture(_Texture);
            Matrix world = Matrix.CreateFromYawPitchRoll(Rotation.X, Rotation.Y, Rotation.Z) * Matrix.CreateTranslation(Position);
            _CliffRenderer.UpdateCliff(_Positions, _IsCliffTrans, _Direction, _Side, world);
            IsCreateShadow = true;
            IsTerrain = true;
            this.CalculateRenderQueueValue();
        }

        public override ContainmentType BeContained(BoundingBox BoundingBox)
        {
            return BoundingBox.Contains(_BoundingBox);
        }

        public override void Update(BoundingFrustum BoundingFrustum)
        {
            if (BoundingFrustum.Contains(this._BoundingBox) != ContainmentType.Disjoint)
                IsInBoundingFrustum = true;
            else
                IsInBoundingFrustum = false;
        }

        public override void Draw(Matrix view, Matrix projection)
        {
            if (IsInBoundingFrustum == false)
                return;

            CliffRenderer renderer = this.PaletteReference.PaletteRenderer as CliffRenderer;
            renderer.Draw(Matrix.Identity, view, projection);
            //_CliffRenderer.Draw(Matrix.Identity, view, projection);
        }
    }

    public class DoodadInstance : PaletteInstance
    {
        public bool IsTree
        {
            get;
            set;
        }

        private BoundingBox _BoundingBox;
        public BoundingBox BoundingBox
        {
            get
            {
                return _BoundingBox;
            }
        }

        protected Texture2D _ReplacableTexture;

        public Texture2D ReplacableTexture
        {
            set { _ReplacableTexture = value; }
            get { return _ReplacableTexture; }
        }

        public DoodadInstance(Vector3 Position, Vector3 Scale, Vector3 Rotation, ERenderState RenderStates, DoodadRenderer palette)
            : base(Position, Rotation, RenderStates, palette)
        {
            this.IsTree = false;
            this.Scale = Scale;

            this.Player = new SequenceAnimationPlayer(palette.Model.Sequences);

            if (this.Player != null)
            {
                int toPlayIndex = this.Player.FindName("Stand");
                if (toPlayIndex != -1)
                {
                    this.Player.SetAnimationClip(toPlayIndex);
                    this.Player.Start(1.0f);
                }
                else
                {
                    //throw new Exception("Stand animation is not found.");
                }
            }

            ModelRenderer renderer = this.PaletteReference.PaletteRenderer as ModelRenderer;
            Matrix world = Orientation *
               Matrix.CreateScale(this.Scale) *
               Matrix.CreateTranslation(this.Position);
            List<Vector3> points = new List<Vector3>();
            foreach (var p in renderer.BoundingBox.GetCorners())
            {
                Vector3 newP = Vector3.Transform(p, world);
                points.Add(newP);
            }
            this._BoundingBox = BoundingBox.CreateFromPoints(points);
            this.CalculateRenderQueueValue();
        }

        public override void Update(GameTime gameTime)
        {
            if (this.Player != null)
                this.Player.Update(gameTime.ElapsedGameTime);
        }

        public override void Update(BoundingFrustum BoundingFrustum)
        {
            if (BoundingFrustum.Contains(this._BoundingBox) != ContainmentType.Disjoint)
                IsInBoundingFrustum = true;
            else
                IsInBoundingFrustum = false;
        }

        public override void Draw(Matrix view, Matrix projection)
        {
            if (IsInBoundingFrustum == false)
                return;

            ModelRenderer renderer = this.PaletteReference.PaletteRenderer as ModelRenderer;
            if (_ReplacableTexture != null)
                renderer.UpdateRelpacableTexture(_ReplacableTexture);
            Matrix world = Orientation *
                           Matrix.CreateScale(this.Scale) *
                           Matrix.CreateTranslation(this.Position);

            if (this.Player != null)
            {
                renderer.UpdateAnimationTick(this.Player.CurrentTick);
            }

            float value = SceneManager.Instance.Game.GameScene.GetComponentInChildren<Landscape>().WarCraftEnvironment.GetLightValue(this.Position);
            EffectManager.Instance.Shader.Fog = value;

            renderer.Draw(world, view, projection);

            EffectManager.Instance.Shader.Fog = 1.0f;
        }
    }

    public class UnitInstance : DoodadInstance
    {
        public UnitInstance(Vector3 Position, Vector3 Scale, Vector3 Rotation, ERenderState RenderStates, UnitRenderer palette)
            : base(Position, Scale, Rotation, RenderStates, palette)
        {
            if (this.Player != null)
            {
                this.Player = new SequenceAnimationPlayer(palette.Model.Sequences);

                int toPlayIndex = this.Player.FindName("stand");
                if (toPlayIndex == -1)
                    toPlayIndex = this.Player.FindName("stand 1");
                if (toPlayIndex == -1)
                    toPlayIndex = this.Player.FindName("stand - 1");
                if (toPlayIndex != -1)
                {
                    this.Player.SetAnimationClip(toPlayIndex);
                    this.Player.Start(1.0f);
                }
                else
                {
                    this.Player.SetAnimationClip(0);
                    this.Player.Start(1.0f);
                    //throw new Exception("Stand animation is not found.");
                }
                this.CalculateRenderQueueValue();
            }
        }

        public override void Update(GameTime gameTime)
        {
            if (this.Player != null)
                this.Player.Update(gameTime.ElapsedGameTime);
        }

        public override void Update(BoundingFrustum BoundingFrustum)
        {
            if (BoundingFrustum.Contains(this.Position) != ContainmentType.Disjoint)
                IsInBoundingFrustum = true;
            else
                IsInBoundingFrustum = false;
        }
    }
    #endregion
}
